/**
 * Moondance UML modeling tool
 * 
 * @author Dascalu Laurentiu (dascalu.laurentziu@gmail.com).
 * 
 * This program is free software; you can redistribute it and
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package ro.pub.cs.se.moondance.xmi.converter;

import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import ro.pub.cs.se.moondance.MoondanceData;
import ro.pub.cs.se.moondance.model.AggregationConnection;
import ro.pub.cs.se.moondance.model.AssociationConnection;
import ro.pub.cs.se.moondance.model.ClassFunction;
import ro.pub.cs.se.moondance.model.ClassVariable;
import ro.pub.cs.se.moondance.model.CompositionConnection;
import ro.pub.cs.se.moondance.model.Connection;
import ro.pub.cs.se.moondance.model.DirectedAssociationConnection;
import ro.pub.cs.se.moondance.model.InheritanceConnection;
import ro.pub.cs.se.moondance.model.Parameter;
import ro.pub.cs.se.moondance.model.RealizationConnection;
import ro.pub.cs.se.moondance.model.UMLBaseClass;
import ro.pub.cs.se.moondance.model.UMLEntity;

/**
 * Base class for the XMI representation of Class, Interface and Package.
 */
public abstract class UMLBlockXMI {
	protected String type;
	protected String id;
	protected String name;
	protected Element currentElement;

	public UMLBlockXMI(String type, String name, String id) {
		this.type = type;
		this.name = name;
		this.id = id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	/**
	 * Serializes a variable into an XML DOM node, having root as parent node.
	 */
	protected void serialize(Element root, Document document,
			ClassVariable myVar) {

		/**
		 * 
		 * // Variable class member <ownedAttribute
		 * xmi:id="_C-bKcGcsEd6QTJx0kShhwg" name="property"
		 * visibility="protected" isStatic="true" aggregation="composite">
		 * 
		 * // Variable default value <defaultValue xmi:type="uml:LiteralString"
		 * xmi:id="_hJnWsGcsEd6QTJx0kShhwg" value="0"/>
		 * 
		 * </ownedAttribute>
		 */

		Element elementVar = document.createElement(XMICommon.OWNED_ATTRIBUTE);

		elementVar.setAttribute(XMICommon.XMI_ID, MoondanceData
				.newRandomString());
		elementVar.setAttribute(XMICommon.NAME, myVar.getName());
		elementVar.setAttribute(XMICommon.VISIBILITY, myVar.getVisibility());
		elementVar.setAttribute(XMICommon.TYPE, myVar.getType());

		if (myVar.isStatic())
			elementVar.setAttribute(XMICommon.IS_STATIC, "true");

		elementVar.setAttribute(XMICommon.AGGREGATION,
				XMICommon.AGGREGATION_COMPOSITE);

		String initialValue = myVar.getInitialValue();
		if (initialValue != null && initialValue.length() > 0) {
			Element initialValueElement = document
					.createElement(XMICommon.DEFAULT_VALUE);
			initialValueElement.setAttribute(XMICommon.XMI_TYPE,
					XMICommon.UML_LITERAL_STRING);
			initialValueElement.setAttribute(XMICommon.XMI_ID, MoondanceData
					.newRandomString());
			initialValueElement.setAttribute(XMICommon.VALUE, initialValue);
			elementVar.appendChild(initialValueElement);
		}

		root.appendChild(elementVar);
	}

	/**
	 * Serializes a function into an XML DOM node, having root as parent node.
	 */
	protected void serialize(Element root, Document document,
			ClassFunction myFunc) {

		/**
		 * // Function class member <ownedOperation
		 * xmi:id="_HovkgGcsEd6QTJx0kShhwg" name="operation"
		 * visibility="protected" isStatic="true" isAbstract="true">
		 * 
		 * // Function parameters ( call & return ) <ownedParameter
		 * xmi:id="_b6CTcGcsEd6QTJx0kShhwg" type="_Bvvw0GcsEd6QTJx0kShhwg"
		 * direction="return"/> </ownedOperation>
		 */

		Element elementFunc = document.createElement(XMICommon.OWNED_OPERATION);
		elementFunc.setAttribute(XMICommon.XMI_ID, MoondanceData
				.newRandomString());
		if (myFunc.isDestructor())
			elementFunc.setAttribute(XMICommon.NAME, "~" + myFunc.getName());
		else
			elementFunc.setAttribute(XMICommon.NAME, myFunc.getName());
		elementFunc.setAttribute(XMICommon.VISIBILITY, myFunc.getVisibility());

		if (myFunc.isStatic())
			elementFunc.setAttribute(XMICommon.IS_STATIC, "true");

		if (myFunc.isAbstract())
			elementFunc.setAttribute(XMICommon.IS_ABSTRACT, "true");

		// Add return parameter, if not void
		String returnType = myFunc.getReturnType();
		if (returnType != null && returnType.length() > 0)
			serialize(elementFunc, document, returnType, true);

		// Add calling parameters, if any
		List<Parameter> params = myFunc.getParameters();

		for (Iterator<Parameter> it = params.iterator(); it.hasNext(); serialize(
				elementFunc, document, it.next().getType(), false))
			;
		root.appendChild(elementFunc);
	}

	/**
	 * Serializes a parameter into an XML DOM node.
	 */
	protected void serialize(Element root, Document document, String type,
			boolean returnParameter) {
		Element elementParam = document
				.createElement(XMICommon.OWNED_PARAMETER);
		elementParam.setAttribute(XMICommon.XMI_ID, MoondanceData
				.newRandomString());
		elementParam.setAttribute(XMICommon.TYPE, type);
		if (returnParameter)
			elementParam.setAttribute(XMICommon.DIRECTION, XMICommon.RETURN);
		else
			elementParam.setAttribute(XMICommon.DIRECTION, XMICommon.IN);
		root.appendChild(elementParam);
	}

	protected void serializeConnections(Element root, Document document,
			UMLBaseClass myClass) {
		// Add aggregation/composition members, if any
		for (int i = 0; i < myClass.getSourceConnections().size(); i++) {
			Connection conn = myClass.getSourceConnections().get(i);
			UMLEntity target = conn.getTarget();
			UMLBaseClass targetBaseClass = null;
			UMLConnectionXMI xmiConn = null;
			String newID = MoondanceData.newRandomString();
			conn.setXMIid(newID);

			if (target instanceof UMLBaseClass)
				targetBaseClass = (UMLBaseClass) target;

			if (conn instanceof AssociationConnection) {
				xmiConn = new UMLAssociationConnectionXMI(conn.getName(),
						newID, myClass.getXMIid(), targetBaseClass.getXMIid(),
						"1", "1");
				xmiConn.serialize(root, document);
			} else if (conn instanceof DirectedAssociationConnection) {
				xmiConn = new UMLDirectAssociationConnectionXMI(conn.getName(),
						newID, myClass.getXMIid(), targetBaseClass.getXMIid(),
						"1", "1");
				xmiConn.serialize(root, document);
			} else if (conn instanceof CompositionConnection
					|| conn instanceof AggregationConnection) {
				StringTokenizer strtok = new StringTokenizer(conn
						.getSourceMultiplicity(), ".,[]");

				String lowerSourceValue = strtok.nextToken();
				String upperSourceValue = lowerSourceValue;

				try {
					upperSourceValue = strtok.nextToken();
				} catch (Exception e) {

				}

				strtok = new StringTokenizer(conn.getTargetMultiplicity(),
						".,[]");
				String lowerDestinationValue = strtok.nextToken();
				String upperDestinationValue = lowerDestinationValue;

				try {
					upperDestinationValue = strtok.nextToken();
				} catch (Exception e) {

				}

				if (conn instanceof AggregationConnection)
					xmiConn = new UMLAggregationConnectionXMI(conn.getName(),
							newID, myClass.getXMIid(), targetBaseClass
									.getXMIid(), "", "",
							XMICommon.UML_LITERAL_STRING,
							XMICommon.UML_LITERAL_STRING, upperSourceValue,
							lowerSourceValue, XMICommon.UML_LITERAL_STRING,
							XMICommon.UML_LITERAL_STRING,
							upperDestinationValue, lowerDestinationValue);
				else
					xmiConn = new UMLCompositeConnectionXMI(conn.getName(),
							newID, myClass.getXMIid(), targetBaseClass
									.getXMIid(), "", "",
							XMICommon.UML_LITERAL_STRING,
							XMICommon.UML_LITERAL_STRING, upperSourceValue,
							lowerSourceValue, XMICommon.UML_LITERAL_STRING,
							XMICommon.UML_LITERAL_STRING,
							upperDestinationValue, lowerDestinationValue);
				xmiConn.serialize(root, document);
			} else if (conn instanceof RealizationConnection) {
				xmiConn = new UMLRealizationConnectionXMI(conn.getName(),
						newID, myClass.getXMIid(), targetBaseClass.getXMIid());
				xmiConn.serialize(root, document);
			} else if (conn instanceof InheritanceConnection) {
				Element newRoot = root;
				searchedID = myClass.getXMIid();

				while (newRoot.getParentNode() != null
						&& newRoot.getParentNode() instanceof Element)
					newRoot = (Element) newRoot.getParentNode();

				dfsSearch(newRoot);

				xmiConn = new UMLInheritanceConnectionXMI(newID,
						targetBaseClass.getXMIid());
				xmiConn.serialize(searchedElement, document);
			}
		}
	}

	private Element searchedElement = null;
	private String searchedID = null;

	private void dfsSearch(Element root) {
		if (root == null)
			return;

		if (root.getAttribute(XMICommon.XMI_ID).equals(searchedID)) {
			searchedElement = root;
		}

		NodeList children = root.getChildNodes();
		for (int i = 0; i < children.getLength(); i++)
			if (children.item(i) instanceof Element)
				dfsSearch((Element) children.item(i));
	}
}
